package com.ouc.pampas;

import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import io.terminus.pampas.engine.utils.Yamls;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.yaml.snakeyaml.Yaml;

@Component
public class ConfigParser {
	private static final Logger log = LoggerFactory.getLogger(ConfigParser.class);
	private static final Yaml YAML = Yamls.getInstance();
	private static final Pattern IMPORT_PATTERN = Pattern.compile("^#import\\s+(.+)$");
	@Autowired
	private FileLoaderHelper fileLoaderHelper;
	private final Map<String, BaseConfig> atomConfigCache = Maps.newHashMap();

	public <T extends BaseConfig> T parseConfig(App app, Class<T> configClass)
			throws IOException, InstantiationException, IllegalAccessException {
		String configFilePath;
		if (configClass == FrontConfig.class) {
			if (Strings.isNullOrEmpty(app.getAssetsHome())) {
				log.warn("app [{}] have no assetsHome", app.getKey());
				return null;
			}
			configFilePath = app.getAssetsHome() + "front_config.yaml";
		} else {
			if (Strings.isNullOrEmpty(app.getConfigPath())) {
				log.warn("app [{}] have no configPath", app.getKey());
				return null;
			}
			configFilePath = app.getConfigPath();
		}
		if (checkNeedReload(configFilePath)) {
			T config = parseConfig0(app, configFilePath, configClass);
			if ((config instanceof FrontConfig)) {
				postFrontConfig(app, (FrontConfig) config);
			} else {
				postBackConfig(app, (BackConfig) config);
			}
			return config;
		}
		return null;
	}

	private boolean checkNeedReload(String configFilePath) {
		BaseConfig cachedConfig = (BaseConfig) this.atomConfigCache.get(configFilePath);
		if (cachedConfig == null) {
			return true;
		}
		if (!this.fileLoaderHelper.load(configFilePath, cachedConfig.getSign()).isNotModified()) {
			return true;
		}
		for (String importFilePath : cachedConfig.getImported()) {
			if (checkNeedReload(importFilePath)) {
				return true;
			}
		}
		return false;
	}

	@SuppressWarnings("unchecked")
	private <T extends BaseConfig> T parseConfig0(App app, String path, Class<T> configClass)
			throws IOException, IllegalAccessException, InstantiationException {
		BaseConfig config = (BaseConfig) this.atomConfigCache.get(path);
		FileLoader.Resp resp;
		if (config == null) {
			resp = this.fileLoaderHelper.load(path);
		} else {
			resp = this.fileLoaderHelper.load(path, config.getSign());
		}
		if (resp.isNotFound()) {
			log.warn("config not found for app [{}] and path [{}]", app.getKey(), path);
		} else if (!resp.isNotModified()) {
			String configStr = resp.asString();

			config = (BaseConfig) configClass.newInstance();
			config.setLocation(path);
			config.setSign(resp.getSign());
			config.setLoadedAt(DateTime.now().toDate());
			this.atomConfigCache.put(path, config);

			BufferedReader bufferedReader = new BufferedReader(new StringReader(configStr));
			String line;
			while ((line = bufferedReader.readLine()) != null) {
				if (!line.trim().equals("")) {
					Matcher matcher = IMPORT_PATTERN.matcher(line);
					if (!matcher.matches()) {
						break;
					}
					String importFileName = matcher.group(1);
					String importFilePath = resolvePath(path, importFileName + ".yaml");
					config.getImported().add(importFilePath);
					T importConfig = parseConfig0(app, importFilePath, configClass);
					config.merge(importConfig);
				}
			}
			config.merge((BaseConfig) YAML.loadAs(configStr, configClass));
		}
		return (T) config;
	}

	private String resolvePath(String originPath, String relativePath) {
		String originFolderPath = originPath.substring(0, originPath.lastIndexOf("/") + 1);
		return originFolderPath + relativePath;
	}

	private void postBackConfig(App app, BackConfig backConfig) {
		backConfig.setApp(app.getKey());
		if (backConfig.getServices() != null) {
			for (Service service : backConfig.getServices().values()) {
				if (Strings.isNullOrEmpty(service.getApp())) {
					service.setApp(app.getKey());
				}
			}
		}
	}

	private void postFrontConfig(App app, FrontConfig frontConfig) {
		frontConfig.setApp(app.getKey());
		if (frontConfig.getComponents() != null) {
			for (String path : frontConfig.getComponents().keySet()) {
				com.ouc.pampas.Component component = (com.ouc.pampas.Component) frontConfig
						.getComponents().get(path);
				component.setPath(path);
				if (component.getCategory().isDesignable()) {
					List<com.ouc.pampas.Component> componentList = (List) frontConfig
							.getComponentCategoryListMap().get(component.getCategory());
					if (componentList == null) {
						componentList = Lists.newArrayList();
						frontConfig.getComponentCategoryListMap().put(component.getCategory(), componentList);
					}
					componentList.add(component);
				}
			}
		}
		if ((frontConfig.getAuths() != null) && (frontConfig.getAuths().getWhiteList() != null)) {
			for (WhiteAuth whiteAuth : frontConfig.getAuths().getWhiteList()) {
				whiteAuth.setRegexPattern(Pattern.compile("^" + whiteAuth.getPattern() + "$"));
			}
		}
		if ((frontConfig.getAuths() != null) && (frontConfig.getAuths().getProtectedList() != null)) {
			for (ProtectedAuth protectedAuth : frontConfig.getAuths().getProtectedList()) {
				protectedAuth.setRegexPattern(Pattern.compile("^" + protectedAuth.getPattern() + "$"));
			}
		}
	}
}
